using BepuPhysics;
using BepuPhysics.Collidables;
using BepuPhysics.CollisionDetection;
using BepuPhysics.Constraints;
using BepuUtilities.Collections;
using System.Runtime.CompilerServices;
using System.Threading;

namespace Demos.Demos.Tanks
{
    /// <summary>
    /// 在坦克演示中存储有关实体的特性。
    /// </summary>
    public struct TankDemoBodyProperties
    {
        /// <summary>
        /// 控制身体可以碰撞的可碰撞对象。
        /// </summary>
        public SubgroupCollisionFilter Filter;
        /// <summary>
        /// 用于车身的摩擦系数。
        /// </summary>
        public float Friction;
        /// <summary>
        /// 如果主体是投射物并且应该在接触时爆炸,则为True。
        /// </summary>
        public bool Projectile;
        /// <summary>
        /// 如果主体是坦克的一部分,则为True。
        /// </summary>
        public bool TankPart;
    }

    public struct ProjectileImpact
    {
        /// <summary>
        /// 与此撞击相关的抛射体的句柄。
        /// </summary>
        public BodyHandle ProjectileHandle;
        /// <summary>
        /// 与投射物击中的任何物体相关联的坦克车身的句柄。如果炮弹没有击中坦克,这是-1。
        /// </summary>
        public BodyHandle ImpactedTankBodyHandle;
    }

    /// <summary>
    /// 对于坦克演示,我们既需要轮身碰撞过滤,也需要车轮与坦克车身的不同摩擦力。
    /// </summary>
    struct TankCallbacks : INarrowPhaseCallbacks
    {
        public CollidableProperty<TankDemoBodyProperties> Properties;
        public SpinLock ProjectileLock;
        public QuickList<ProjectileImpact> ProjectileImpacts;
        public void Initialize(Simulation simulation)
        {
            Properties.Initialize(simulation);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool AllowContactGeneration(int workerIndex, CollidableReference a, CollidableReference b)
        {
            // 两个静力学是不可能相撞的,对的排序是这样的,物体总是排在静力学之前。
            if (b.Mobility != CollidableMobility.Static)
            {
                return SubgroupCollisionFilter.AllowCollision(Properties[a.BodyHandle].Filter, Properties[b.BodyHandle].Filter);
            }
            return a.Mobility == CollidableMobility.Dynamic || b.Mobility == CollidableMobility.Dynamic;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool AllowContactGeneration(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB)
        {
            // 对于复合体的子级、网格中的三角形以及类似的情况都会调用此函数,但我们在坦克演示中不执行子级别的过滤。
            // 顶层过滤总是会在这个函数有机会运行之前运行,所以我们在这里什么都不用做。
            return true;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        void TryAddProjectileImpact(BodyHandle projectileHandle, CollidableReference impactedCollidable)
        {
            bool lockTaken = false;
            ProjectileLock.Enter(ref lockTaken);
            try
            {
                // 请注意,我们必须防止冗余添加-一发炮弹可能会击中同一帧中的多个物体。不希望它多次爆炸。
                for (int i = 0; i < ProjectileImpacts.Count; ++i)
                {
                    ref var impact = ref ProjectileImpacts[i];
                    // 如果投射物已经处理过,请忽略它。
                    if (impact.ProjectileHandle.Value == projectileHandle.Value)
                        return;
                }
                // 爆炸射弹列表应该提前调整大小以容纳所有射弹,因此不需要动态分配。
                ref var newImpact = ref ProjectileImpacts.AllocateUnsafely();
                newImpact.ProjectileHandle = projectileHandle;
                if (impactedCollidable.Mobility != CollidableMobility.Static)
                {
                    // 过滤的组ID是坦克的主体句柄。我们用它来找到坦克(如果这具身体与坦克有任何关系的话)。
                    ref var properties = ref Properties[impactedCollidable.BodyHandle];
                    newImpact.ImpactedTankBodyHandle = new BodyHandle(properties.TankPart ? properties.Filter.GroupId : -1);
                }
                else
                {
                    // 它击中了一辆静电;坦克不是静电。
                    newImpact.ImpactedTankBodyHandle = new BodyHandle(-1);
                }
            }
            finally
            {
                if (lockTaken)
                    ProjectileLock.Exit();
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe bool ConfigureContactManifold<TManifold>(int workerIndex, CollidablePair pair, ref TManifold manifold, out PairMaterialProperties pairMaterial) where TManifold : struct, IContactManifold<TManifold>
        {
            // 不同的油箱部件具有不同的摩擦值。轮子往往比坦克车身粘得更多。
            ref var propertiesA = ref Properties[pair.A.BodyHandle];
            pairMaterial.FrictionCoefficient = propertiesA.Friction;
            if (pair.B.Mobility != CollidableMobility.Static)
            {
                // 如果两个物体相撞,只要取平均摩擦力即可。其他选项包括min(a,b)或a*b。
                ref var propertiesB = ref Properties[pair.B.BodyHandle];
                pairMaterial.FrictionCoefficient = (pairMaterial.FrictionCoefficient + propertiesB.Friction) * 0.5f;
            }
            // 这些只是一些不错的标准值。较高的最大速度可能会导致在深度接触过程中引入更多能量。
            // 在困难的情况下,有限弹簧刚度有助于解算器收敛到解。尽量将弹簧频率保持在时间步长频率的一半左右或更低。
            pairMaterial.MaximumRecoveryVelocity = 2f;
            pairMaterial.SpringSettings = new SpringSettings(30, 1);

            if (propertiesA.Projectile || (pair.B.Mobility != CollidableMobility.Static && Properties[pair.B.BodyHandle].Projectile))
            {
                for (int i = 0; i < manifold.Count; ++i)
                {
                    // 这看起来可能有点奇怪。您不能在结构中返回对This实例的引用,接口也不能需要静电函数.
                    // 因此,我们使用这种冗余结构来直接引用联系人的深度,而开销几乎为零。
                    // 接触属性也有更典型的输出参数重载。而且总是可以选择直接使用多重指针。
                    // 请注意非零负阈值的使用：投机性接触将使传入的对象在表面停止,但在某些情况下,积分器/数字问题可能意味着它们不能完全到达。
                    // 在大多数情况下,这根本不是问题,但坦克炮弹移动非常快,一帧未命中的画面可能就足以不引发爆炸。
                    // 非零ε有助于捕捉这些案例。
                    // (另一种选择是检查每个炮弹的接触约束,如果任何接触具有非零侵彻冲量,则会导致爆炸。)
                    if (manifold.GetDepth(ref manifold, i) >= -1e-3f)
                    {
                        // 发现了实际的碰撞。
                        if (propertiesA.Projectile)
                        {
                            TryAddProjectileImpact(pair.A.BodyHandle, pair.B);
                        }
                        if (pair.B.Mobility != CollidableMobility.Static && Properties[pair.B.BodyHandle].Projectile)
                        {
                            // 可以在两个身体都是抛射物的情况下从技术上结合锁,但这并不是很常见。
                            TryAddProjectileImpact(pair.B.BodyHandle, pair.A);
                        }
                        break;
                    }
                }
            }
            return true;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe bool ConfigureContactManifold(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB, ref ConvexContactManifold manifold)
        {
            return true;
        }

        public void Dispose()
        {
            Properties.Dispose();
        }
    }
}
